#region Copyright and Licensing Notice
// Copyright (C) 2011 Steven Bosnick
// 
// This program is open source software; you can redistribute it 
// and/or modifiy it under the terms of the Eclipse Public License,
// version 1.0.
// 
// This program is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A 
// PARTICULAR PURPOSE. See the Eclipse Public License for more
// details.
// 
// You should have received a copy of the Eclipse Public License 
// along with this program; if not, you can find a copy at
// "http://www.opensource.org/licenses/eclipse-1.0.php".
#endregion
using System;
using System.Runtime.InteropServices;
using BitwiseOre.SharedMemory.Core;
using BitwiseOre.SharedMemory.Core.Primatives;
using NUnit.Framework;
using Moq;

namespace BitwiseOre.Tests.SharedMemory.Primatives
{
    [TestFixture]
    public sealed class SharedAllocatorFixture : AssertionHelper
    {
        [Test(Description = "Ensures that attempting to allocate an int array with a non-positive count " +
                            "throws an exception")]
        public void AllocateIntArray_WithNonPositiveCount()
        {
            const int count = 0;
            var allocator = new SharedAllocator(null, (o, c) => null);

            Expect(() => allocator.AllocateIntArray(count), 
                Throws.InstanceOf<ArgumentOutOfRangeException>());
        }

        [Test(Description = "Checks that the initial allocation occurs at offset 0")]
        public void AllocateIntArray_InitialAllocation()
        {
            const int expectedoffset = 0;
            int? offset = null;
            IntArrayCreator source = 
                (o, c) => { offset = o; return new Mock<IIntArray>().Object; };
            var allocator = new SharedAllocator(source, (o, c) => null);

            allocator.AllocateIntArray(5);

            Expect(offset.HasValue, "There was no request to create an int array.");
            Expect(offset, EqualTo(expectedoffset),
                "Unexpected offset used to create the int array.");
        }

        [Test(Description = "Checks that a second allocation occurs at an offset after the first")]
        public void AllocateIntArray_SusequentAllocation()
        {
            const int expectedoffset = 50;
            var array = new Mock<IIntArray>();
            array.SetupGet(a => a.Capacity).Returns(expectedoffset);
            int? offset = null;
            IntArrayCreator creator =
                (o, c) => { offset = o; return array.Object; };
            var allocator = new SharedAllocator(creator, (o, c) => null);

            allocator.AllocateIntArray(5);
            allocator.AllocateIntArray(7);

            Expect(offset, EqualTo(expectedoffset),
                "Unexpected offset used to create the int array.");
        }

        [Test(Description = "Checks that the count is used to create the int array in an allocation")]
        public void AllocateIntArray_UsesCountToAllocate()
        {
            const int count = 5;
            int? actualcount = null;
            IntArrayCreator creator =
                (o, c) => { actualcount = c; return new Mock<IIntArray>().Object; };
            var allocator = new SharedAllocator(creator, (o, c) => null);

            allocator.AllocateIntArray(count);

            Expect(actualcount.HasValue, "There was not request to create an int array.");
            Expect(actualcount, EqualTo(count),
                "Unexpected count used to create the int array.");
        }

        [Test(Description = "Checks that the created int array is used as the result of the allocation")]
        public void AllocateIntArray_ReturnsCreateIntArray()
        {
            var array = new Mock<IIntArray>();
            IntArrayCreator creator = (o, c) => array.Object;
            var allocator = new SharedAllocator(creator, (o, c) => null);

            var result = allocator.AllocateIntArray(5);

            Expect(result, EqualTo(array.Object));
        }

        [Test(Description = "Ensures that attempting to allocate a general array with a non-postive count " +
                            "throws an exception")]
        public void AllocateArray_WithNonPositiveCount()
        {
            const int count = 0;
            var allocator = new SharedAllocator(null, (o, c) => null);

            Expect(() => allocator.AllocateArray<TimeSpan>(count),
                   Throws.InstanceOf<ArgumentOutOfRangeException>());
        }

        [Test(Description = "Checks that the initial allocation occurs at offset 0")]
        public void AllocateArray_InitialAllocation()
        {
            const int expectedoffset = 0;
            int? offset = null;
            AccessorCreator creator = (o, c) =>
                                          {
                                              offset = o;
                                              return new Mock<IUnmanagedMemoryAccessor>().Object;
                                          };
            var allocator = new SharedAllocator(null, creator);

            allocator.AllocateArray<TimeSpan>(5);

            Expect(offset, EqualTo(expectedoffset),
                "Unexpected offset used to create the accessor for the array.");
        }

        [Test(Description = "Checks that a subsequent allocation occurs after the inital allocation")]
        public void AllocatArray_SubsequentAllocation()
        {
            const int expectedoffset = 50;
            var accessor = new Mock<IUnmanagedMemoryAccessor>();
            accessor.SetupGet(a => a.Capacity).Returns(expectedoffset);
            int? offset = null;
            AccessorCreator creator = (o, c) =>
                                          {
                                              offset = o;
                                              return accessor.Object;
                                          };
            var allocator = new SharedAllocator(null, creator);

            allocator.AllocateArray<TimeSpan>(5);
            allocator.AllocateArray<TimeSpan>(7);

            Expect(offset, EqualTo(expectedoffset),
                "Unexpected offset used to create the accessor for the array.");
        }

        [Test(Description = "Checks that the count and the size of the allocation type are used to create the array")]
        public void AllocateArray_UsesCountAndSizeOfTToAllocate()
        {
            const int expectedcount = 5;
            var sizeoftimespan = Marshal.SizeOf(typeof (TimeSpan));
            int? capacity = null;
            AccessorCreator creator = (o, c) =>
                                          {
                                              capacity = c;
                                              return new Mock<IUnmanagedMemoryAccessor>().Object;
                                          };
            var allocator = new SharedAllocator(null, creator);

            allocator.AllocateArray<TimeSpan>(expectedcount);

            Expect(capacity, EqualTo(expectedcount*sizeoftimespan),
                "Unexpected capacity used to create the accessor for the array.");
        }

        [Test(Description = "Checks that a subsequent array allocation occurs after an intial int array allocation")]
        public void AllocateArray_AfterAllocateIntArray()
        {
            const int expectedoffset = 50;
            var intarray = new Mock<IIntArray>();
            intarray.SetupGet(a => a.Capacity).Returns(expectedoffset);
            int? offset = null;
            AccessorCreator creator = (o, c) =>
                                          {
                                              offset = o;
                                              return new Mock<IUnmanagedMemoryAccessor>().Object;
                                          };
            var allocator = new SharedAllocator((o, c) => intarray.Object, creator);

            allocator.AllocateIntArray(5);
            allocator.AllocateArray<TimeSpan>(7);

            Expect(offset, EqualTo(expectedoffset),
                "Unexpected offset used to create the accessor for use in the subsequent array allocation.");
        }

        [Test(Description = "Checks that a subsequent int array allocation occurs after an inital array allocation")]
        public void AllocateIntArray_AfterAllocateArray()
        {
            const int expectedoffset = 50;
            var accessor = new Mock<IUnmanagedMemoryAccessor>();
            accessor.SetupGet(a => a.Capacity).Returns(expectedoffset);
            int? offset = null;
            IntArrayCreator creator = (o, c) =>
                                          {
                                              offset = o;
                                              return new Mock<IIntArray>().Object;
                                          };
            var allocator = new SharedAllocator(creator, (o, c) => accessor.Object);

            allocator.AllocateArray<TimeSpan>(5);
            allocator.AllocateIntArray(7);

            Expect(offset, EqualTo(expectedoffset),
                "Unexpected offset used to create the subseqent int array.");
        }

        [Test(Description = "Ensures that attempting to allocate a byte array with a non-positive count " +
                            "throws an exception.")]
        public void AllocateByteArray_WithNonPostiveCount()
        {
            const int count = 0;
            var allocator = new SharedAllocator(null, (o, c) => null);

            Expect(() => allocator.AllocateByteArray(count),
                   Throws.InstanceOf<ArgumentOutOfRangeException>());
        }

        [Test(Description = "Checks that the inital allocation of a byte array occurs at offset 0")]
        public void AllocateByteArray_InitialAllocation()
        {
            var offset = -1;
            var accessor = new Mock<IUnmanagedMemoryAccessor>().Object;
            var allocator = new SharedAllocator(null, (o, c) =>
                                                          {
                                                              offset = o;
                                                              return accessor;
                                                          });

            allocator.AllocateByteArray(2);

            Expect(offset, EqualTo(0), "Array created at unexpected offset.");
        }

        [Test(Description = "Checks that a subseqeunt allocation of a byte array occurs at an offset " +
                            "after the initial allocation")]
        public void AllocateByteArray_SubsequentAllocation()
        {
            var offset = -1;
            const int count1 = 5;
            const int count2 = 2;
            var accessor = new Mock<IUnmanagedMemoryAccessor>();
            accessor.SetupGet(a => a.Capacity).Returns(count1);
            var allocator = new SharedAllocator(null, (o, c) =>
                                                          {
                                                              offset = o;
                                                              return accessor.Object;
                                                          });

            allocator.AllocateByteArray(count1);
            allocator.AllocateByteArray(count2);

            Expect(offset, EqualTo(count1), "Array created at unexpected offset.");
            
        }

        [Test(Description = "Checks that allocating a byte array allocates the number of bytes needed " +
                            "for the array.")]
        public void AllocateByteArray_AllocatesCountBytes()
        {
            var bytecount = -1;
            const int count = 5;
            var accessor = new Mock<IUnmanagedMemoryAccessor>().Object;
            var allocator = new SharedAllocator(null, (o, c) =>
                                                          {
                                                              bytecount = c;
                                                              return accessor;
                                                          });

            allocator.AllocateByteArray(count);

            Expect(bytecount, EqualTo(count), "Unexpected number of bytes allocated.");
        }

    }
}